#include "Pch.h"

#include "TriangleBufferIntersect.h"

#include "IntersectTriangle.h"
#include "../TriangleBuffer.h"

#include <vector>

namespace IS
{

class TriangleBufferIntersect::Impl
{
public:
	Impl() : mTribufs(nullptr) {}
	~Impl() {}

	LinkList<TriangleBuffer>* mTribufs;

	std::vector<TriangleBufferIntersect::Hit> mHits;
	size_t mClosestHit;
};
	
TriangleBufferIntersect::TriangleBufferIntersect()
	: mImpl(new Impl)
{
}

TriangleBufferIntersect::~TriangleBufferIntersect()
{
	delete mImpl;
}

void TriangleBufferIntersect::beginIntersect(LinkList<TriangleBuffer>& tribufs)
{
	mImpl->mTribufs = &tribufs;

	for(TriangleBuffer* tribuf = mImpl->mTribufs->begin()
		; tribuf != mImpl->mTribufs->end()
		; tribuf = tribuf->next())
	{
		tribuf->beginUpdate();
	}
}

size_t TriangleBufferIntersect::intersect(const Vec3f& orig, const Vec3f& dir, bool twosided)
{
	if(nullptr == mImpl->mTribufs)
		return 0;

	mImpl->mHits.clear();
	mImpl->mClosestHit = 0xffffffff;

	for(TriangleBuffer* tribuf = mImpl->mTribufs->begin()
		; tribuf != mImpl->mTribufs->end()
		; tribuf = tribuf->next())
	{
		const size_t cTriCnt = tribuf->getTriangleCount();

		for(size_t itri = 0; itri < cTriCnt; ++itri)
		{
			uint16_t* idx = tribuf->getTriangleIndexAt(itri);

			Vec3f v0 = tribuf->getPositionAt(idx[0]);
			Vec3f v1 = tribuf->getPositionAt(idx[1]);
			Vec3f v2 = tribuf->getPositionAt(idx[2]);

			tribuf->localTransform.transformPoint(v0);
			tribuf->localTransform.transformPoint(v1);
			tribuf->localTransform.transformPoint(v2);

			float t, u, v;

			if(1 == intersectTriangle<float>(orig, dir, v0, v1, v2, &t, &u, &v, twosided))
			{
				if( t < 0 )
					continue;

				Hit hit;
				hit.faceIdx = itri;
				hit.t = t;
				hit.u = u;
				hit.v = v;
				hit.w = (1.0f - u - v);
				hit.tribuf = tribuf;

				if(mImpl->mHits.empty())
				{
					mImpl->mClosestHit = mImpl->mHits.size();
				}
				else
				{
					if(t < mImpl->mHits[mImpl->mClosestHit].t)
						mImpl->mClosestHit = mImpl->mHits.size();
				}

				mImpl->mHits.push_back(hit);

#if _DEBUG
                {   // some verifications
                    Hit& lastHit = mImpl->mHits.back();
                    uint16_t idx0 = lastHit.tribuf->getTriangleIndexAt(lastHit.faceIdx)[0];
                    uint16_t idx1 = lastHit.tribuf->getTriangleIndexAt(lastHit.faceIdx)[1];
                    uint16_t idx2 = lastHit.tribuf->getTriangleIndexAt(lastHit.faceIdx)[2];
                    MCD_VERIFY(idx0 == idx[0]);
                    MCD_VERIFY(idx1 == idx[1]);
                    MCD_VERIFY(idx2 == idx[2]);

                    Vec3f pos1 = orig + t * dir;
                    Vec3f pos2 = lastHit.w * v0 + lastHit.u * v1 + lastHit.v * v2;
                    MCD_VERIFY( pos1.distance(pos2) < 1e-3f );
                }
#endif // _DEBUG

			}
		}
	}

	return mImpl->mHits.size();
}

size_t TriangleBufferIntersect::getClosestHitIndex() const
{
	return mImpl->mClosestHit;
}

const TriangleBufferIntersect::Hit & TriangleBufferIntersect::getHit(size_t i) const
{
	return mImpl->mHits[i];
}

Vec3f TriangleBufferIntersect::getHitPosition(size_t i) const
{
    const Hit& hit = getHit(i);
	const uint16_t* vidx = hit.tribuf->getTriangleIndexAt(hit.faceIdx);

    Vec3f v0 = hit.tribuf->getPositionAt(vidx[0]);
    Vec3f v1 = hit.tribuf->getPositionAt(vidx[1]);
    Vec3f v2 = hit.tribuf->getPositionAt(vidx[2]);
    hit.tribuf->localTransform.transformPoint(v0);
    hit.tribuf->localTransform.transformPoint(v1);
    hit.tribuf->localTransform.transformPoint(v2);
    
    return hit.w * v0 + hit.u * v1 + hit.v * v2;
}

Vec3f TriangleBufferIntersect::getHitNormal(size_t i) const
{
    const Hit& hit = getHit(i);
	const uint16_t* vidx = hit.tribuf->getTriangleIndexAt(hit.faceIdx);

    Vec3f v0 = hit.tribuf->getNormalAt(vidx[0]);
    Vec3f v1 = hit.tribuf->getNormalAt(vidx[1]);
    Vec3f v2 = hit.tribuf->getNormalAt(vidx[2]);
    hit.tribuf->localTransform.transformNormal(v0);
    hit.tribuf->localTransform.transformNormal(v1);
    hit.tribuf->localTransform.transformNormal(v2);
    
    return hit.w * v0 + hit.u * v1 + hit.v * v2;
}

Vec4f TriangleBufferIntersect::getHitColor(size_t i) const
{
    const Hit& hit = getHit(i);
	const uint16_t* vidx = hit.tribuf->getTriangleIndexAt(hit.faceIdx);

    Vec4f v0 = hit.tribuf->getColorAt(vidx[0]);
    Vec4f v1 = hit.tribuf->getColorAt(vidx[1]);
    Vec4f v2 = hit.tribuf->getColorAt(vidx[2]);
    
    return hit.w * v0 + hit.u * v1 + hit.v * v2;
}

void TriangleBufferIntersect::endIntersect()
{
	for(TriangleBuffer* tribuf = mImpl->mTribufs->begin()
		; tribuf != mImpl->mTribufs->end()
		; tribuf = tribuf->next())
	{
		tribuf->endUpdate();
	}

	mImpl->mTribufs = nullptr;
}

}	// namespace IS